package grpc

import (
	"context"
	"crypto/x509"
	"fmt"
	"net"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/keepalive"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/peer"
	"hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/crypto"
	"hundsun.com/hsl/hschain/rpc/common/rpcerror"
)

const (
	configKeyConnectTimeout                                 = "grpc.connect.timeout"
	configKeyMaxSendMsgSize                                 = "grpc.maxSendMsgSize"
	configKeyMaxRecvMsgSize                                 = "grpc.maxRecvMsgSize"
	configKeyMaxConcurrentStreams                           = "grpc.maxConcurrentStreams"
	configKeyClientKeepAliveTime                            = "grpc.keepalive.client.time"
	configKeyClientKeepAliveTimeout                         = "grpc.keepalive.client.timeout"
	configKeyClientKeepAlivePermitWithoutStream             = "grpc.keepalive.client.permitWithoutStream"
	configKeyServerKeepAliveTime                            = "grpc.keepalive.server.time"
	configKeyServerKeepAliveTimeout                         = "grpc.keepalive.server.timeout"
	configKeyServerKeepEnforcementPolicyMinTime             = "grpc.keepalive.server.enforcementPolicy.minTime"
	configKeyServerKeepEnforcementPolicyPermitWithoutStream = "grpc.keepalive.server.enforcementPolicy.permitWithoutStream"
)

const (
	defaultConnectTimeout   = 3
	defaultKeepAliveTime    = 600
	defaultKeepAliveTimeout = 3
	defaultMaxSendMsgSize   = 1024 * 1024 * 40 // 40M
	defaultMaxRecvMsgSize   = 1024 * 1024 * 40 // 40M
)

const (
	constGrpcMetadataCerKey = "cer-file-bin"
)

var connectTimeout time.Duration
var clientKeepAliveParams keepalive.ClientParameters
var serverKeepAliveParams keepalive.ServerParameters
var serverKeepAliveEnforcementPolicy keepalive.EnforcementPolicy
var maxSendMsgSize int
var maxRecvMsgSize int
var maxConcurrentStreams int

// grpc 默认配置参数不能依赖与创世区块中的参数
func init() {

	connectTimeout = time.Duration(config.GetIntConfigValue(configKeyConnectTimeout, defaultConnectTimeout)) * time.Second

	clientKeepAliveParams = keepalive.ClientParameters{
		Time:                time.Duration(config.GetIntConfigValue(configKeyClientKeepAliveTime, defaultKeepAliveTime)) * time.Second,
		Timeout:             time.Duration(config.GetIntConfigValue(configKeyClientKeepAliveTimeout, defaultKeepAliveTimeout)) * time.Second,
		PermitWithoutStream: config.GetBoolConfigValue(configKeyClientKeepAlivePermitWithoutStream, true),
	}

	serverKeepAliveParams = keepalive.ServerParameters{
		Time:    time.Duration(config.GetIntConfigValue(configKeyServerKeepAliveTime, defaultKeepAliveTime)) * time.Second,
		Timeout: time.Duration(config.GetIntConfigValue(configKeyServerKeepAliveTimeout, defaultKeepAliveTimeout)) * time.Second,
	}

	serverKeepAliveEnforcementPolicy = keepalive.EnforcementPolicy{
		MinTime:             time.Duration(config.GetIntConfigValue(configKeyServerKeepEnforcementPolicyMinTime, defaultKeepAliveTimeout/2)) * time.Second,
		PermitWithoutStream: config.GetBoolConfigValue(configKeyServerKeepEnforcementPolicyPermitWithoutStream, true),
	}
	maxSendMsgSize = config.GetIntConfigValue(configKeyMaxSendMsgSize, defaultMaxSendMsgSize)
	maxRecvMsgSize = config.GetIntConfigValue(configKeyMaxRecvMsgSize, defaultMaxRecvMsgSize)
	maxConcurrentStreams = config.GetIntConfigValue(configKeyMaxConcurrentStreams, 100)
}

//GetKeepAliveClientDialOption ...
func GetKeepAliveClientDialOption() grpc.DialOption {
	return grpc.WithKeepaliveParams(clientKeepAliveParams)
}

//GetKeepAliveServerOption ...
func GetKeepAliveServerOption() grpc.ServerOption {
	return grpc.KeepaliveParams(serverKeepAliveParams)
}

//GetKeepAliveEnforcementPolicyServerOption ...
func GetKeepAliveEnforcementPolicyServerOption() grpc.ServerOption {
	return grpc.KeepaliveEnforcementPolicy(serverKeepAliveEnforcementPolicy)
}

//GetConnectTimeoutClientDialOption ...
func GetConnectTimeoutClientDialOption() grpc.DialOption {
	return grpc.WithTimeout(connectTimeout)
}

//GetMaxConcurrentStreamsServerOption ...
func GetMaxConcurrentStreamsServerOption() grpc.ServerOption {
	return grpc.MaxConcurrentStreams(uint32(maxConcurrentStreams))
}

// GetMsgSizeServerOptions 获取服务端消息大小选项
func GetMsgSizeServerOptions() []grpc.ServerOption {
	return []grpc.ServerOption{
		grpc.MaxSendMsgSize(maxSendMsgSize),
		grpc.MaxRecvMsgSize(maxRecvMsgSize),
	}
}

// GetMsgSizeClientCallOptions 获取客户端消息大小选项
func GetMsgSizeClientCallOptions() []grpc.CallOption {
	return []grpc.CallOption{
		grpc.MaxRecvMsgSizeCallOption{MaxRecvMsgSize: maxRecvMsgSize},
		grpc.MaxSendMsgSizeCallOption{MaxSendMsgSize: maxSendMsgSize},
	}
}

//GetDefaultServerOptions 获取默认服务器选项，是此package中如下方法的组合:
// GetMsgSizeServerOptions();
// GetKeepAliveEnforcementPolicyServerOption();
// GetKeepAliveServerOption();
// GetMaxConcurrentStreamsServerOption()
func GetDefaultServerOptions() []grpc.ServerOption {
	opts := GetMsgSizeServerOptions()
	opts = append(opts, GetKeepAliveEnforcementPolicyServerOption())
	opts = append(opts, GetKeepAliveServerOption())
	opts = append(opts, GetMaxConcurrentStreamsServerOption())
	return opts
}

//GetDefaultClientDialOptions 获取默认客户端选项，是此package中如下方法的组合:
// GetMsgSizeClientCallOptions();
// GetKeepAliveClientDialOption();
// GetConnectTimeoutClientDialOption();
func GetDefaultClientDialOptions() []grpc.DialOption {
	var opts []grpc.DialOption
	opts = append(opts, grpc.WithDefaultCallOptions(GetMsgSizeClientCallOptions()...))
	opts = append(opts, GetKeepAliveClientDialOption())
	opts = append(opts, GetConnectTimeoutClientDialOption())
	return opts
}

//AuthOption auth option config
type AuthOption struct{}

//GetRequestMetadata ...
func (a AuthOption) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	cerFile, err := crypto.GetLocalCertFile()
	if err != nil || cerFile == nil {
		return nil, err
	}
	return map[string]string{
		constGrpcMetadataCerKey: string(cerFile),
	}, nil
}

//RequireTransportSecurity ...
func (a AuthOption) RequireTransportSecurity() bool {
	return false
}

//GetAuthOption metadata中加入cert
func GetAuthOption() grpc.DialOption {
	return grpc.WithPerRPCCredentials(new(AuthOption))
}

//GetClientIP context中获取ip
func GetClientIP(ctx context.Context) (string, error) {
	pr, ok := peer.FromContext(ctx)
	if !ok {
		return "", rpcerror.Errorf("[getClientIP] invoke FromContext() failed")
	}
	if pr.Addr == net.Addr(nil) {
		return "", fmt.Errorf("[getClientIP] peer.Addr is nil")
	}
	return pr.Addr.String(), nil
}

//AuthVerify grpc 验签, caBuf在chain.enableAuthVerify未false的情况下允许为null, privateVerify允许为空
func AuthVerify(ctx context.Context, caBuf []byte, privateVerify func(ctx context.Context, certificate *x509.Certificate) (bool, error)) (bool, error) {
	if caBuf == nil {
		return false, fmt.Errorf("cannot find parent certificate file")
	}

	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return false, fmt.Errorf("auth verify:get metadata from context failed")
	}
	cerStrs := md.Get(constGrpcMetadataCerKey)
	if len(cerStrs) == 0 {
		return false, fmt.Errorf("cannot find cer file in metadata")
	}
	//cer string转byte[]
	certBuf := []byte(cerStrs[0])
	//验证是否由父证书签发
	ok, err := crypto.CheckSignatureFrom(caBuf, certBuf)
	if err != nil || !ok {
		return ok, err
	}
	//个性化校验
	cer, err := crypto.CertFromBytes(certBuf)
	if err != nil || cer == nil {
		return false, fmt.Errorf("decode certification file failed")
	}
	if crypto.EnablePrivateCa() && privateVerify != nil {
		return privateVerify(ctx, cer)
	}
	return true, nil
}

//GetCertFromContext 获取grpc请求中的证书
func GetCertFromContext(ctx context.Context) (certificate *x509.Certificate, err error) {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, fmt.Errorf("get metadata from context failed")
	}
	cerStrs := md.Get(constGrpcMetadataCerKey)
	if len(cerStrs) == 0 {
		return nil, fmt.Errorf("cannot find cer file in metadata")
	}
	//cer string转byte[]
	certBuf := []byte(cerStrs[0])
	cer, err := crypto.CertFromBytes(certBuf)
	if err != nil || cer == nil {
		return nil, fmt.Errorf("decode certification file failed")
	}
	return cer, nil
}
